#include "../../Header/LOD/cLODSelection.h"
#include "../../Header/misc.h"
#include "../../Header/ShaderObject.h"
#include "../../Header/glErrorUtil.h"
#include "../../Header/Camera.h"
#include <GL/glew.h>

cLODSelection::cLODSelection()
{
	
	posVBOLODs = 0;
	posTexBufferId = 0;
	numElements = 0;
	numAgentsWidth = 0;
	numAgentsHeight=0;

	VFCLodAssigmentTex = new ShaderObject("shaders/LOD/VFCLodAssigmentTexVertex.glsl", "shaders/LOD/VFCLodAssigmentTexGeometry.glsl", GL_POINTS, GL_POINTS);
	VFCLodAssigmentTex->activate();
		int loc2[] =
		{
			glGetVaryingLocationNV(VFCLodAssigmentTex->shader_id, "gl_Position"),
		};
		glTransformFeedbackVaryingsNV(VFCLodAssigmentTex->shader_id, 1, loc2, GL_SEPARATE_ATTRIBS_NV);	
		VFCLodAssigmentTex->setUniformi("position", 0);
		VFCLodAssigmentTex->setUniformi("idTex", 1);
	VFCLodAssigmentTex->deactivate();

	lodSelection = new ShaderObject("shaders/LOD/VFCLodAssigmentTexVertex.glsl", "shaders/LOD/lodSelectionGeometry.glsl", GL_POINTS, GL_POINTS);
	lodSelection->activate();
		int loc3[] =
		{
			glGetVaryingLocationNV(lodSelection->shader_id, "gl_Position"),
		};
		glTransformFeedbackVaryingsNV(lodSelection->shader_id, 1, loc3, GL_SEPARATE_ATTRIBS_NV);	
	lodSelection->deactivate();

	drawVBOTextureBuffer = new ShaderObject("shaders/LOD/drawTextureBufferVertex.glsl", "shaders/LOD/drawTextureBufferFragment.glsl");
	drawVBOTextureBuffer->activate();
		drawVBOTextureBuffer->setUniformi("posTextureBuffer",0);
	drawVBOTextureBuffer->deactivate();



}
cLODSelection::~cLODSelection()
{
	int i;
	FREE_VBO(texCoords);
	for (i=0;i<numElements;i++)
	{
		if (posVBOLODs)
			FREE_VBO(posVBOLODs[i]);
		if (posTexBufferId)
			FREE_TEXTURE(posTexBufferId[i]);
	}
	FREE_ARRAY(posVBOLODs);
	FREE_ARRAY(posTexBufferId);
	glDeleteQueries(1, &query);

	FREE_MEMORY(lodSelection);
	FREE_MEMORY(VFCLodAssigmentTex);
	FREE_MEMORY(drawVBOTextureBuffer);

}

// init when not using instancing or crowds
void cLODSelection::init(int numAgentsWidth_, int numAgentsHeight_, int _numLODs) 
{
	int i;
	numAgentsWidth=numAgentsWidth_;
	numAgentsHeight=numAgentsHeight_;
	numLODs=_numLODs;
	texCoords=0;
	
	posVBOLODs = new unsigned int [numElements = numLODs+1];
	posTexBufferId = new unsigned int[numLODs+1];
	for (i=0;i<numLODs+1;i++)
	{
		posVBOLODs[i]=0;
		posTexBufferId[i] = 0;

		CheckErrorsGL("BEGIN : cLODSelection::cLODSelection VBO");
			glGenBuffers(1, &posVBOLODs[i]);
			glBindBuffer(GL_ARRAY_BUFFER, posVBOLODs[i]);
			glBufferData(GL_ARRAY_BUFFER, numAgentsWidth*numAgentsHeight*4*sizeof(GLfloat), 0, GL_STATIC_DRAW);
			glBindBuffer(GL_ARRAY_BUFFER, 0);
		CheckErrorsGL("END : cLODSelection::cLODSelection VBO");
	
		CheckErrorsGL("BEGIN : cLODSelection::cLODSelection glTexBufferEXT ");
			glGenTextures(1,&posTexBufferId[i]);
			glBindTexture(GL_TEXTURE_BUFFER_EXT, posTexBufferId[i]);
			glTexBufferEXT(GL_TEXTURE_BUFFER_EXT, GL_RGBA32F_ARB, posVBOLODs[i]);
			glBindTexture(GL_TEXTURE_BUFFER_EXT, 0);
		CheckErrorsGL("END : cLODSelection::cLODSelection glTexBufferEXT");
	}
	glGenQueries(1, &query);
}

//init when using Crowds. In this case VFC and LOD Assigment is done in the Crowd Class
float *test[4];
void cLODSelection::initForCrowds(int characterPerGroup, int _numLODs) 
{
	int i;
	numLODs=_numLODs;
	texCoords=0;
	
	posVBOLODs = new unsigned int [numElements = numLODs];
	posTexBufferId = new unsigned int[numLODs];
	for (i=0;i<numLODs;i++)
	{
		posVBOLODs[i]=0;
		posTexBufferId[i] = 0;

		CheckErrorsGL("BEGIN : cLODSelection::cLODSelection VBO");
			glGenBuffers(1, &posVBOLODs[i]);
			glBindBuffer(GL_ARRAY_BUFFER, posVBOLODs[i]);
			glBufferData(GL_ARRAY_BUFFER, characterPerGroup*4*sizeof(GLfloat), 0, GL_STATIC_DRAW);
			glBindBuffer(GL_ARRAY_BUFFER, 0);
		CheckErrorsGL("END : cLODSelection::cLODSelection VBO");
	
		CheckErrorsGL("BEGIN : cLODSelection::cLODSelection glTexBufferEXT ");
			glGenTextures(1,&posTexBufferId[i]);
			glBindTexture(GL_TEXTURE_BUFFER_EXT, posTexBufferId[i]);
			glTexBufferEXT(GL_TEXTURE_BUFFER_EXT, GL_RGBA32F_ARB, posVBOLODs[i]);
			glBindTexture(GL_TEXTURE_BUFFER_EXT, 0);
		CheckErrorsGL("END : cLODSelection::cLODSelection glTexBufferEXT");
	}


	glGenQueries(1, &query);
}

// For Stats use
void cLODSelection::VFC_LOD_Assignation (unsigned int target, unsigned int posID, Camera *cam)
{
	float X[3], Y[3], Z[3], ratio;
	cam->updatePlanes(X,Y,Z, ratio);

	if (!texCoords)
	{
		initTexCoords(target);
		VFCLodAssigmentTex->activate();
			VFCLodAssigmentTex->setUniformf("tang", cam->tang);
			VFCLodAssigmentTex->setUniformf("farPlane", cam->far_plane);
			VFCLodAssigmentTex->setUniformf("nearPlane", cam->near_plane);
		VFCLodAssigmentTex->deactivate();
	}
	
	// Culling and assigning LOD level, this should be done in the shader/kernel that computes new positions
	
	CheckErrorsGL("cLODSelection VFC & LOD Assigment :BEGIN");
	VFCLodAssigmentTex->activate();

		glBindBufferBaseNV(GL_TRANSFORM_FEEDBACK_BUFFER_NV, 0, posVBOLODs[cLODSelection::VBO_CULLED]);
		glBeginTransformFeedbackNV(GL_POINTS);
		glEnable(GL_RASTERIZER_DISCARD_NV);    // disable rasterization
		glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV, query);

		VFCLodAssigmentTex->setUniformf("ratio", ratio);
		VFCLodAssigmentTex->setUniformfv("X",X,3);
		VFCLodAssigmentTex->setUniformfv("Y",Y,3);
		VFCLodAssigmentTex->setUniformfv("Z",Z,3);
		VFCLodAssigmentTex->setUniformfv("camPos", cam->pos, 3);
		
		// Drawing
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(target, posID);

		
		glBindBuffer(GL_ARRAY_BUFFER_ARB,texCoords);
		glVertexPointer(2, GL_FLOAT, 0, 0);
		glEnableClientState(GL_VERTEX_ARRAY);

			glDrawArrays(GL_POINTS,0,numAgentsWidth*numAgentsHeight);

		glDisableClientState(GL_VERTEX_ARRAY);
		glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
		
		
		glBindTexture(target, 0);
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(target, 0);

		glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV);
		glDisable(GL_RASTERIZER_DISCARD_NV);
		glEndTransformFeedbackNV();
		glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitivesWritten[cLODSelection::VBO_CULLED]);

	VFCLodAssigmentTex->deactivate();
	CheckErrorsGL("cLODSelection VFC & LOD Assigment :END");	

}

// For Stats use
void cLODSelection::LODSorting(struct sVBOLod *vboLOD)
{
	int i;
	for (i=0; i<numLODs;i++)
	{	
		CheckErrorsGL("cLODSelection LOD  Selection :BEGIN");
		lodSelection->activate();

			glBindBufferBaseNV(GL_TRANSFORM_FEEDBACK_BUFFER_NV, 0, posVBOLODs[i+1]);
			glBeginTransformFeedbackNV(GL_POINTS);
			glEnable(GL_RASTERIZER_DISCARD_NV);    // disable rasterization
			glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV, query);

			lodSelection->setUniformf("lod", i+1.0f);

			// Drawing
			glBindBuffer(GL_ARRAY_BUFFER_ARB ,posVBOLODs[cLODSelection::VBO_CULLED]);
			glVertexPointer(4, GL_FLOAT, 0, 0);
			glEnableClientState(GL_VERTEX_ARRAY);
				glDrawArrays(GL_POINTS,0,primitivesWritten[cLODSelection::VBO_CULLED]);
			glDisableClientState(GL_VERTEX_ARRAY);
			glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
		
			glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV);
			glDisable(GL_RASTERIZER_DISCARD_NV);
			glEndTransformFeedbackNV();
			glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitivesWritten[i+1]);
		lodSelection->deactivate();
		CheckErrorsGL("cLODSelection LOD  Selection :END");	
		vboLOD[i].id = posTexBufferId[i+1]; 
		//vboLOD[i].id = posVBOLODs[i+1];
		vboLOD[i].primitivesWritten = primitivesWritten[i+1];

	}
}


// When not using Crowds
void cLODSelection::runSelection(unsigned int target, unsigned int posID, struct sVBOLod *vboLOD, Camera *cam)
{
	int i;
	float X[3], Y[3], Z[3], ratio;
	cam->updatePlanes(X,Y,Z, ratio);
	
	
	if (target==GL_ARRAY_BUFFER)
	{
		lodSelection->activate();

			//CheckErrorsGL("cLODSelection::runSelection :BEGIN");
			/*
			glBindBufferBaseNV(GL_TRANSFORM_FEEDBACK_BUFFER_NV, 0, posVBOLODs[0]);
			glBeginTransformFeedbackNV(GL_POINTS);
			glEnable(GL_RASTERIZER_DISCARD_NV);    // disable rasterization
			glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV, query);

*/
			glBindBuffer(GL_ARRAY_BUFFER_ARB,posID);

			glVertexPointer(4, GL_FLOAT, 0, 0);
			glEnableClientState(GL_VERTEX_ARRAY);
				glDrawArrays(GL_POINTS,0,numAgentsWidth*numAgentsHeight);
			glDisableClientState(GL_VERTEX_ARRAY);
			glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
/*
			glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV);
			glDisable(GL_RASTERIZER_DISCARD_NV);
			glEndTransformFeedbackNV();
			glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitivesWritten);
*/
		lodSelection->deactivate();
		
	}
	else
	{
	
		if (!texCoords)
		{
			initTexCoords(target);
			VFCLodAssigmentTex->activate();
				VFCLodAssigmentTex->setUniformf("tang", cam->tang);
				VFCLodAssigmentTex->setUniformf("farPlane", cam->far_plane);
				VFCLodAssigmentTex->setUniformf("nearPlane", cam->near_plane);
			VFCLodAssigmentTex->deactivate();
		}
		
		// Culling and assigning LOD level, this should be done in the shader/kernel that computes new positions
		
		CheckErrorsGL("cLODSelection VFC & LOD Assigment :BEGIN");
		VFCLodAssigmentTex->activate();

			glBindBufferBaseNV(GL_TRANSFORM_FEEDBACK_BUFFER_NV, 0, posVBOLODs[cLODSelection::VBO_CULLED]);
			glBeginTransformFeedbackNV(GL_POINTS);
			glEnable(GL_RASTERIZER_DISCARD_NV);    // disable rasterization
			glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV, query);

			VFCLodAssigmentTex->setUniformf("ratio", ratio);
			VFCLodAssigmentTex->setUniformfv("X",X,3);
			VFCLodAssigmentTex->setUniformfv("Y",Y,3);
			VFCLodAssigmentTex->setUniformfv("Z",Z,3);
			VFCLodAssigmentTex->setUniformfv("camPos", cam->pos, 3);
			
			// Drawing
			glActiveTexture(GL_TEXTURE0);
			glBindTexture(target, posID);

			
			glBindBuffer(GL_ARRAY_BUFFER_ARB,texCoords);
			glVertexPointer(2, GL_FLOAT, 0, 0);
			glEnableClientState(GL_VERTEX_ARRAY);

				glDrawArrays(GL_POINTS,0,numAgentsWidth*numAgentsHeight);

			glDisableClientState(GL_VERTEX_ARRAY);
			glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
			
			
			glBindTexture(target, 0);
			glActiveTexture(GL_TEXTURE0);
			glBindTexture(target, 0);
	
			glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV);
			glDisable(GL_RASTERIZER_DISCARD_NV);
			glEndTransformFeedbackNV();
			glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitivesWritten[cLODSelection::VBO_CULLED]);

		VFCLodAssigmentTex->deactivate();
		CheckErrorsGL("cLODSelection VFC & LOD Assigment :END");	

		// four passes, waiting for multiple tranform feedback targets extension
		i = 0;
		for (i=0; i<numLODs;i++)
		{	
			CheckErrorsGL("cLODSelection LOD  Selection :BEGIN");
			lodSelection->activate();

				glBindBufferBaseNV(GL_TRANSFORM_FEEDBACK_BUFFER_NV, 0, posVBOLODs[i+1]);
				glBeginTransformFeedbackNV(GL_POINTS);
				glEnable(GL_RASTERIZER_DISCARD_NV);    // disable rasterization
				glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV, query);

				lodSelection->setUniformf("lod", i+1.0f);

				// Drawing
				glBindBuffer(GL_ARRAY_BUFFER_ARB ,posVBOLODs[cLODSelection::VBO_CULLED]);
				glVertexPointer(4, GL_FLOAT, 0, 0);
				glEnableClientState(GL_VERTEX_ARRAY);
					glDrawArrays(GL_POINTS,0,primitivesWritten[cLODSelection::VBO_CULLED]);
				glDisableClientState(GL_VERTEX_ARRAY);
				glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
			
				glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV);
				glDisable(GL_RASTERIZER_DISCARD_NV);
				glEndTransformFeedbackNV();
				glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitivesWritten[i+1]);
			lodSelection->deactivate();
			CheckErrorsGL("cLODSelection LOD  Selection :END");	
			vboLOD[i].id = posTexBufferId[i+1]; 
			//vboLOD[i].id = posVBOLODs[i+1];
			vboLOD[i].primitivesWritten = primitivesWritten[i+1];

		}
	}
	
}


// VFC and LOD Selection is done in the Crowd Class
void cLODSelection::runSelection(unsigned int posVFCwithLOD, struct sVBOLod *vboLOD, unsigned int primitivesWritten, Camera *cam, int animationId)
{
int i;
	float X[3], Y[3], Z[3], ratio;
	cam->updatePlanes(X,Y,Z, ratio);
	
	for (i=0; i<numLODs;i++)
	{	
		CheckErrorsGL("cLODSelection LOD  Selection :BEGIN");
		lodSelection->activate();

			glBindBufferBaseNV(GL_TRANSFORM_FEEDBACK_BUFFER_NV, 0, posVBOLODs[i]);
			glBeginTransformFeedbackNV(GL_POINTS);
			glEnable(GL_RASTERIZER_DISCARD_NV);    // disable rasterization
			glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV, query);

			lodSelection->setUniformf("lod", i+1.0f);
			lodSelection->setUniformf("animationId", animationId * 1.0f);

			// Drawing
			glBindBuffer(GL_ARRAY_BUFFER_ARB ,posVFCwithLOD);
			glVertexPointer(4, GL_FLOAT, 0, 0);
			glEnableClientState(GL_VERTEX_ARRAY);
				glDrawArrays(GL_POINTS,0,primitivesWritten);
			glDisableClientState(GL_VERTEX_ARRAY);
			glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
		
			glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV);
			glDisable(GL_RASTERIZER_DISCARD_NV);
			glEndTransformFeedbackNV();
			glGetQueryObjectuiv(query, GL_QUERY_RESULT, &vboLOD[i].primitivesWritten);
		lodSelection->deactivate();
		CheckErrorsGL("cLODSelection LOD  Selection :END");	
		vboLOD[i].id = posTexBufferId[i]; 
		//vboLOD[i].id = posVBOLODs[i+1];
	}
/*	
	glBindBuffer(GL_ARRAY_BUFFER_ARB ,posVFCwithLOD);
	test[0] = (float *) glMapBuffer( GL_ARRAY_BUFFER, GL_READ_ONLY );
	glUnmapBuffer( GL_ARRAY_BUFFER );
	glBindBuffer(GL_ARRAY_BUFFER_ARB ,0);

	float *pix[4];

	for (i=0;i<numLODs;i++)
	{
		glBindBuffer(GL_ARRAY_BUFFER_ARB ,posTexBufferId[i]);
		test[i] = (float *) glMapBuffer( GL_ARRAY_BUFFER, GL_READ_ONLY );
		glUnmapBuffer( GL_ARRAY_BUFFER );
		glBindBuffer(GL_ARRAY_BUFFER_ARB ,0);
	}
	/*
	for (i=0;i<numLODs;i++)
	{
		pix[i] = new float [64*64*4];
		glBindTexture(GL_TEXTURE_BUFFER_EXT, vboLOD[i].id);
		glReadPixels(	0,0, 64,64, GL_RGBA, GL_FLOAT, pix[i]);
		
	}
	glBindTexture(GL_TEXTURE_BUFFER_EXT, 0);
	*/
}

#define TEXTURE_BUFFER
void cLODSelection::draw(struct sVBOLod *vboLOD, float *color)
{

#ifdef TEXTURE_BUFFER
	CheckErrorsGL("cLODSelection::draw () glTexBufferEXT : BEGIN");

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_BUFFER_EXT, vboLOD->id);

	glBindBuffer(GL_ARRAY_BUFFER_ARB,texCoords);
	glVertexPointer(2, GL_FLOAT, 0, 0);
	glEnableClientState(GL_VERTEX_ARRAY);
	drawVBOTextureBuffer->activate();
		drawVBOTextureBuffer->setUniformfv("color", color, 3); 
		glDrawArrays(GL_POINTS,0,vboLOD->primitivesWritten);
	drawVBOTextureBuffer->deactivate();
	glDisableClientState(GL_VERTEX_ARRAY);
	glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
	
	glBindTexture(GL_TEXTURE_BUFFER_EXT,  0);
	CheckErrorsGL("cLODSelection::draw () glTexBufferEXT : END");
#else
	
	CheckErrorsGL("cLODSelection::draw () BUffer Array : BEGIN");

		
	glBindBuffer(GL_ARRAY_BUFFER_ARB,vboLOD->id);
	glVertexPointer(4, GL_FLOAT, 0, 0);
	glEnableClientState(GL_VERTEX_ARRAY);
	drawVBO->activate();
		glDrawArrays(GL_POINTS,0,vboLOD->primitivesWritten);
	drawVBO->deactivate();	
	glDisableClientState(GL_VERTEX_ARRAY);
	
	glBindBuffer(GL_ARRAY_BUFFER_ARB,0);

	CheckErrorsGL("cLODSelection::draw () BUffer Array : END");

#endif
}

void cLODSelection::initTexCoords(unsigned int target)
{
	CheckErrorsGL("cLODSelection::initTexCoords2D():BEGIN");
	int no_vertices=2*(numAgentsWidth)*(numAgentsHeight);
	float *coords=  new float[no_vertices];
	//creating coords pointer, Just Points 
	int i,j,count=0;
	for(i=0; i<numAgentsWidth; i++) 
		for(j=0; j<numAgentsHeight; j++)
		{
			if (target == GL_TEXTURE_2D)
			{
				coords[count  ]=i/((numAgentsWidth)*1.0f);
				coords[count+1]=j/((numAgentsHeight)*1.0f);
				
			}
			else if (target == GL_TEXTURE_RECTANGLE_ARB || target == GL_TEXTURE_RECTANGLE_EXT || target == GL_TEXTURE_RECTANGLE_NV)
			{
				coords[count  ]=i*1.0f;
				coords[count+1]=j*1.0f;
			}
			else 
			{
				coords[count  ]=0.0f;
				coords[count+1]=0.0f;
			}
			count+=2;
		}
	// Generate And Bind The coords Buffer if USING VBO
	glGenBuffersARB(1,&texCoords);				// Get A Valid Name
	glBindBufferARB(GL_ARRAY_BUFFER_ARB,texCoords);		// Bind The Buffer
	// Load The Data
	glBufferDataARB(GL_ARRAY_BUFFER_ARB,no_vertices*sizeof(float),coords,GL_STATIC_DRAW_ARB);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);		// unBind The Buffer
	//deleting the coords data from CPU memory
	FREE_ARRAY(coords);
	CheckErrorsGL("cLODSelection::initTexCoords2D():END");
}
